﻿/******************************************************************************
 * \brief   画一个圆，然后用Bresenham算法对圆进行填充
 * \details 在Windows下使用VS编译，直接使用Windows API(C/C++)，不使用MFC、WPF等框架；
 *          画圆使用Windows API，填充用画线的API自行实现
 * \note    UTF-8 BOM编码
 * \author  将狼才鲸
 * \date    2023-01-31
 * \remarks
 *      参考网址：
 *      [基于 Bresenham 算法画填充圆](http://www.javashuo.com/article/p-oiizeyjd-de.html)
 ******************************************************************************/

/*********************************** 头文件 ***********************************/
#include <windows.h>    /* Windows API的头文件 */
#include <stdio.h>      /* printf */

/*********************************** 宏定义 ***********************************/
#define FPS 60  /* 帧率，Frames per Second */
#define DEFAULT_RADIUS_RATIO    4   /* 默认的气泡半径是窗口宽度的多少分之1 */

/********************************** 类型定义 **********************************/

/********************************** 全局变量 **********************************/
static WCHAR titleName[] = L"Windows API Demo"; /* 窗口标题文字 */
static int g_window_x, g_window_y;      /* 窗口宽度和高度，单位为像素 */
static int g_x = 100, g_y = 100;        /* 圆心坐标，单位为像素 */
static int g_fno;                       /* 帧序号，对每一帧显示进行计数 */
static int default_radius = 10;         /* 当前圆的默认半径，单位为像素 */
static HANDLE hThread;  /* 创建的绘图线程 */
static HPEN hPenWhite;  /* 白色画笔 */
static HPEN hPenRed;    /* 红色画笔 */
static HPEN hPenGreen;  /* 绿色画笔 */
static int ms_per_frame = 1000 / FPS;   /* 每帧所占据的时间，单位为ms */

/********************************** 私有函数 **********************************/
/**
 * \brief   基于 Bresenham 算法画填充圆
 * \param   x，y = 圆心坐标；r = 半径
 */
void FillCircle_Bresenham(HDC hdc, int x, int y, int r)
{
    int tx = 0, ty = r, d = 3 - 2 * r;

    while (tx < ty)
    {
        // 小于 45 度横线
        MoveToEx(hdc, x - ty, y - tx, NULL);    LineTo(hdc, x + ty, y - tx);
        if (tx != 0)    // 防止水平线重复绘制
        {
            MoveToEx(hdc, x - ty, y + tx, NULL);    LineTo(hdc, x + ty, y + tx);
        }

        if (d < 0)      // 取上面的点
        {
            d += 4 * tx + 6;
        }
        else            // 取下面的点
        {
            // 大于 45 度横线
            MoveToEx(hdc, x - tx, y - ty, NULL);    LineTo(hdc, x + tx, y - ty);
            MoveToEx(hdc, x - tx, y + ty, NULL);    LineTo(hdc, x + tx, y + ty);
            d += 4 * (tx - ty) + 10;
            ty--;
        }
        tx++;
    }

    if(tx == ty)        // 45 度横线
    {
        MoveToEx(hdc, x - ty, y - tx, NULL);    LineTo(hdc, x + ty, y - tx);
        MoveToEx(hdc, x - ty, y + tx, NULL);    LineTo(hdc, x + ty, y + tx);
    }
}

/**
 * \brief   在窗口中刷新一帧内容
 */
static int window_update(HDC hdc)
{
    static WCHAR text_info[64];     /* 屏幕上显示文字时使用 */
    BOOL ret;   /* 用于拷机时画面无显示时调试用 */

    /* 1. 帧数文字信息显示 */
    wsprintf((LPWSTR)text_info, (LPCWSTR)L"当前帧：%d", g_fno++);
    ret = TextOut(hdc, 10, 10, (LPCWSTR)text_info, wcslen(text_info));
    if (ret != 1)
        printf("error");

    /* 2. 画圆 */
    HPEN hOldPen1 = (HPEN)::SelectObject(hdc, hPenRed);
    /* Windows API画圆和椭圆函数，参数为圆占据的矩形左上角和右下角坐标；横轴向右是增加，纵轴向上是减少 */
    Ellipse(hdc, g_x - default_radius, g_y - default_radius,
        g_x + default_radius, g_y + default_radius);

    /* 3. 填充圆：用一条条直线进行填充 */
    HPEN hOldPen2 = (HPEN)::SelectObject(hdc, hPenGreen);
    MoveToEx(hdc, 1, 1, NULL);    LineTo(hdc, 100, 100);    /* 画一条直线，测试用 */
    FillCircle_Bresenham(hdc, g_x, g_y, default_radius);

    /* 4. 显示BMP图片：LoadImage() */

    /* 5. 拷贝图片（贴图）：BitBlt() */

    return 0;
}

/**
 * \brief   执行窗口内容更新的线程
 */
DWORD WINAPI ThreadProcessFunc(LPVOID lpParamter)
{
    HWND hWnd = (HWND)lpParamter;
    HDC hdc;        /* 窗口信息 */

    /* 窗口合法性判断 */
    hdc = GetDC(hWnd);  /* 选中当前绘图区域，同样也是当前窗口 */
    if (IsIconic(hWnd))
        return 0;

    /* 画笔设置 */
    hPenWhite = (HPEN)::CreatePen(PS_SOLID, 2, RGB(255, 255, 255)); /* 白色线 */
    hPenRed = (HPEN)::CreatePen(PS_SOLID, 2, RGB(176, 48, 96));     /* 红色线 */
    hPenGreen = (HPEN)::CreatePen(PS_SOLID, 2, RGB(34, 139, 34));   /* 绿色线 */

    /* 死循环，持续运行 */
	while (TRUE)
	{
        window_update(hdc);
        //GdiFlush(); /* 及时将绘图区绘制的内容写入到窗口显存中去 */
		Sleep(ms_per_frame); //单位是毫秒
	}

    //TODO: 此处未释放hdc窗口绘图区域，自己临时绘图时，记得GetDC和ReleaseDC成对使用

    return 0;
}

/**
 * \brief   窗口消息处理回调函数
 * \details 如响应窗口的大小变化
 */
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    RECT rect;      /* 窗口大小 */

    /* 1. 处理想要处理的窗口消息 */
    switch (message)
    {
    case WM_CREATE:     /* 窗口被创建时的消息 */
        return 0;

    case WM_SIZE:   /* 适配窗口大小的改变 */
        {
            /* 程序刚运行，窗口刚打开的时候，会自动进入一次 */
            GetClientRect(hWnd, &rect);  /* 获取窗口的大小 */
            g_window_x = rect.right - rect.left;
            g_window_y = rect.bottom - rect.top;
            default_radius = g_window_y / DEFAULT_RADIUS_RATIO;
            g_x = default_radius * 2;
            g_y = default_radius * 2;
        }
        return 0;

    case WM_TIMER:  /* 定时器消息（中断）处理 */
        {
        }
        return 0;

    case WM_PAINT:  /* 最开始绘制窗口中默认显示的内容 */
        break; 

    case WM_CLOSE:      /* 程序退出，先close再destroy */
        {
            TerminateThread(hThread, 0);    /* 强制退出绘图线程 */
            DestroyWindow(hWnd);
        }
        return 0;

    case WM_DESTROY:    /* 程序退出 */
        PostQuitMessage(0); /* 该函数向消息队列中插入一条WM_QUIT消息，由GetMessage函数捕获返回0而退出程序 */
        break;
    }

    /* 为应用程序没有处理的任何窗口消息提供缺省的处理，该函数确保每一个消息都得到处理 */
    return DefWindowProc(hWnd, message, wParam, lParam);
}

/********************************** 接口函数 **********************************/
/**
 * \brief   Windows图形界面程序固定的入口函数
 * \details 如果是命令行函数，则入口函数不一样
 * \remarks
 *      1、注册窗口类 (RegisterClassEx)
 *      2、创建窗口 (CreateWindowsEx)
 *      3、在桌面显示窗口 (ShowWindows)
 *      4、更新窗口客户区 (UpdataWindows)
 *      5、进入无限循环的消息获取和处理的循环：
 *          GetMessage，获取消息
 *          TranslateMessage，转换键盘消息
 *          DispatchMessage，将消息发送到相应的窗口函数
 */
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    /* 1. 设置窗口属性和注册窗口 */
    WNDCLASSEX wcex; /* 定义窗口属性结构体 */
    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;  /* 允许窗口缩放 */
    wcex.lpfnWndProc    = (WNDPROC)WndProc;         /* 窗口消息处理回调函数 */
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = NULL;                     /* 窗口左上角图标的句柄 */
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = (LPCWSTR)titleName;       /* 类名称 */
    wcex.hIconSm        = NULL;                     /* 小图标句柄 */
    RegisterClassEx(&wcex);

    /* 2. 创建窗口 */
    HWND hWnd;
#ifdef COLLISION_ALGORITHM_TEST
    hWnd = CreateWindow((LPCWSTR)titleName, (LPCWSTR)titleName, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 400, 400, NULL, NULL, hInstance, NULL);   /* 窗口指定宽高 */
#else
    /* 当前我的电脑上默认初始窗口大小 1424 * 720，测试碰撞时基于此设置初始位置和速度 */
    hWnd = CreateWindow((LPCWSTR)titleName, (LPCWSTR)titleName, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);   /* 窗口使用默认宽高 */
#endif

    if (!hWnd)
        return FALSE;   /* 如果创建窗口失败则返回 */

    /* 3. 显示窗口和刷新窗口 */
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    /* 4. 添加线程，通过hWnd持续绘制显示区域 */
    /* CreateThread()函数参数介绍：
     * LPSECURITY_ATTRIBUTESlpThreadAttributes, 表示线程内核对象的安全属性，一般传入NULL表示使用默认设置
     * DWORDdwStackSize,    表示线程栈空间大小，传入0表示使用默认大小（1MB）
     * LPTHREAD_START_ROUTINElpStartAddress,    表示新线程所执行的线程函数地址，多个线程可以使用同一个函数地址
     * LPVOIDlpParameter,   是传给线程函数的参数
     * DWORDdwCreationFlags,指定额外的标志来控制线程的创建，为0表示线程创建之后立即就可以进行调度，如果为CREATE_SUSPENDED则表示线程创建后暂停运行，这样它就无法调度，直到调用ResumeThread()
     * LPDWORDlpThreadId    将返回线程的ID号，传入NULL表示不需要返回该线程ID号 */
    hThread = CreateThread(NULL, 0, ThreadProcessFunc, hWnd, 0, NULL);

    /* 5. 循环获取和处理窗口上的消息 */
    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);   /* 转换键盘等产生的消息 */
        DispatchMessage(&msg);    /* 将消息发送到窗口函数 */
    }

    return (int)msg.wParam;
}

/*********************************** 文件尾 ***********************************/
